// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.api.ads.dfp.lib.client;

import com.google.api.ads.common.lib.auth.OAuth2AuthorizationHeaderProvider;
import com.google.api.ads.common.lib.auth.OAuthAuthorizationHeaderProvider;
import com.google.api.ads.common.lib.client.HeaderHandler;
import com.google.api.ads.common.lib.exception.AuthenticationException;
import com.google.api.ads.common.lib.exception.OAuthException;
import com.google.api.ads.common.lib.exception.ServiceException;
import com.google.api.ads.common.lib.soap.SoapClientHandlerInterface;
import com.google.api.ads.dfp.lib.conf.DfpApiConfiguration;
import com.google.api.ads.dfp.lib.conf.DfpBuildConfiguration;
import com.google.api.client.http.GenericUrl;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;

import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;

/**
 * DFP implementation of {@link HeaderHandler}.
 *
 * @author Adam Rogal
 */
public class DfpAxisHeaderHandler implements HeaderHandler<DfpSession, DfpServiceDescriptor> {

  private final SoapClientHandlerInterface<Object> soapClientHandler;
  private final OAuthAuthorizationHeaderProvider oAuthAuthorizationHeaderProvider;
  private final OAuth2AuthorizationHeaderProvider oAuth2AuthorizationHeaderProvider;
  private final DfpApiConfiguration dfpApiConfiguration;
  private final DfpBuildConfiguration dfpBuildConfiguration;

  /**
   * Constructor.
   *
   * @param soapClientHandler the SOAP client handler
   * @param oAuthAuthorizationHeaderProvider the OAuth header provider
   * @param dfpApiConfiguration the DFP API configuration
   */
  @SuppressWarnings("unchecked") // All generics of SoapClientHandlerInterface
                                 // extend Object.
  @Inject
  public DfpAxisHeaderHandler(
      @SuppressWarnings("rawtypes") /* Due to problem with guice binding */
      SoapClientHandlerInterface soapClientHandler,
      OAuthAuthorizationHeaderProvider oAuthAuthorizationHeaderProvider,
      OAuth2AuthorizationHeaderProvider oAuth2AuthorizationHeaderProvider,
      DfpApiConfiguration dfpApiConfiguration,
      DfpBuildConfiguration dfpBuildConfiguration) {
    this.oAuthAuthorizationHeaderProvider = oAuthAuthorizationHeaderProvider;
    this.oAuth2AuthorizationHeaderProvider = oAuth2AuthorizationHeaderProvider;
    this.soapClientHandler = soapClientHandler;
    this.dfpApiConfiguration = dfpApiConfiguration;
    this.dfpBuildConfiguration = dfpBuildConfiguration;
  }

  /**
   * @see HeaderHandler#setHeaders(Object,
   *      com.google.api.ads.common.lib.client.AdsSession,
   *      com.google.api.ads.common.lib.client.AdsServiceDescriptor)
   */
  public void setHeaders(Object soapClient, DfpSession dfpSession,
      DfpServiceDescriptor dfpServiceDescriptor) throws AuthenticationException,
      ServiceException {
    try {
      Object soapHeader = createSoapHeader(dfpServiceDescriptor);

      BeanUtils.setProperty(soapHeader, "applicationName",
          generateLibSignature() + "|" + dfpSession.getApplicationName());
      if (dfpSession.getNetworkCode() != null) {
        BeanUtils.setProperty(soapHeader, "networkCode", dfpSession.getNetworkCode());
      }

      setAuthenticationHeaders(dfpServiceDescriptor, soapClient, soapHeader, dfpSession);

      String namespace =
          dfpApiConfiguration.getNamespacePrefix() + "/"
              + dfpServiceDescriptor.getVersion();
      soapClientHandler.setHeader(soapClient, namespace, "RequestHeader", soapHeader);
    } catch (InstantiationException e) {
      throw new ServiceException("Unexpected exception.", e);
    } catch (IllegalAccessException e) {
      throw new ServiceException("Unexpected exception.", e);
    } catch (ClassNotFoundException e) {
      throw new ServiceException("Unexpected exception.", e);
    } catch (InvocationTargetException e) {
      throw new ServiceException("Unexpected exception.", e);
    } catch (IllegalArgumentException e) {
      throw new ServiceException("Unexpected exception.", e);
    } catch (SecurityException e) {
      throw new ServiceException("Unexpected exception.", e);
    } catch (NoSuchMethodException e) {
      throw new ServiceException("Unexpected exception.", e);
    }
  }

  /**
   * Generates a library signature from the configuration files.
   */
  String generateLibSignature() {
    return dfpBuildConfiguration.getLibPrefix() + "-" + dfpBuildConfiguration.getLibPostfix() + "-"
        + dfpBuildConfiguration.getLibVersion();
  }

  /**
   * Sets the authentication headers.
   *
   * @param dfpServiceDescriptor the DFP service descriptor
   * @param soapClient the SOAP client
   * @param soapHeader the SOAP header
   * @param dfpSession the DFP session
   * @throws IllegalAccessException if there was a problem setting the header
   * @throws InvocationTargetException if there was a problem setting the header
   * @throws OAuthException if there was a problem getting/setting the OAuth
   *     header
   * @throws ClassNotFoundException if there was a problem setting the header
   * @throws NoSuchMethodException if there was a problem setting the header
   * @throws InstantiationException if there was a problem setting the header
   * @throws SecurityException if there was a problem setting the header
   * @throws IllegalArgumentException if there was a problem setting the header
   */
  @VisibleForTesting
  void setAuthenticationHeaders(DfpServiceDescriptor dfpServiceDescriptor, Object soapClient,
      Object soapHeader, DfpSession dfpSession) throws IllegalAccessException,
      InvocationTargetException, OAuthException, ClassNotFoundException, IllegalArgumentException,
      SecurityException, InstantiationException, NoSuchMethodException {
    if (dfpSession.getClientLoginToken() != null) {
      BeanUtils.setProperty(soapHeader, "authentication",
          createClientLoginObject(dfpServiceDescriptor, dfpSession));
    } else if (dfpSession.getOAuthParameters() != null) {
      final String authorizationHeader =
          oAuthAuthorizationHeaderProvider.getOAuthAuthorizationHeader(
              dfpSession, new GenericUrl(soapClientHandler.getEndpointAddress(soapClient)));
      soapClientHandler.setHttpHeaders(soapClient, new HashMap<String, String> () {{
        put("Authorization", authorizationHeader);
      }});
    } else if (dfpSession.getOAuth2Credential() != null) {
      final String authorizationHeader =
          oAuth2AuthorizationHeaderProvider.getOAuth2AuthorizationHeader(dfpSession);
      soapClientHandler.setHttpHeaders(soapClient, new HashMap<String, String> () {{
        put("Authorization", authorizationHeader);
      }});
    }
  }

  /**
   * Creates a SOAP header.
   *
   * @param adsServiceDescriptor the ads service descriptor
   * @return the instantiated SOAP header
   * @throws ClassNotFoundException if the SOAP header class could not be found
   * @throws IllegalAccessException if the SOAP header class could not be
   *         created
   * @throws InstantiationException if the SOAP header class could not be
   *         created
   */
  @VisibleForTesting
  Object createSoapHeader(DfpServiceDescriptor adsServiceDescriptor)
      throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    Class<?> interfaceClass = adsServiceDescriptor.getInterfaceClass();
    String packageName = interfaceClass.getPackage().getName();
    return Class.forName(packageName + ".SoapRequestHeader").newInstance();
  }

  /**
   * Creates a SOAP header.
   *
   * @param dfpServiceDescriptor the ads service descriptor
   * @return the instantiated SOAP header
   * @throws ClassNotFoundException if the client login class could not be found
   * @throws IllegalAccessException if the client login class could not be
   *         created
   * @throws InstantiationException if the client login class could not be
   *         created
   * @throws NoSuchMethodException if the client login class could not be
   *         created
   * @throws InvocationTargetException if the client login class could not be
   *         created
   * @throws SecurityException if the client login class could not be
   *         created
   * @throws IllegalArgumentException if the client login class could not be
   *         created
   */
  @VisibleForTesting
  Object createClientLoginObject(DfpServiceDescriptor dfpServiceDescriptor, DfpSession dfpSession)
      throws InstantiationException, IllegalAccessException, ClassNotFoundException,
      IllegalArgumentException, SecurityException, InvocationTargetException,
      NoSuchMethodException {
    Class<?> interfaceClass = dfpServiceDescriptor.getInterfaceClass();
    String packageName = interfaceClass.getPackage().getName();
    Class<?> clientLoginClass = Class.forName(packageName + "." + "ClientLogin");

    return clientLoginClass.getConstructor(String.class, String.class).newInstance(null,
        dfpSession.getClientLoginToken());
  }
}
